package ch.sv7.tool.classloader;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipClassLoader extends ClassLoader {

	private Hashtable cachedClasses;

	private Vector applicationPackages;

	private String zipFilename;

	private byte classByteBuffer[];

	public ZipClassLoader(String zipFilename, Vector applicationPackages) throws IOException {
		cachedClasses = new Hashtable();
		this.zipFilename = zipFilename;
		this.applicationPackages = applicationPackages;
		ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilename));
		ZipEntry zipEntry = zipInputStream.getNextEntry();
		int maxEntrySize = 0;
		for (; zipEntry != null; zipEntry = zipInputStream.getNextEntry()) {
			zipInputStream.closeEntry();
			int zipEntrySize = (int) zipEntry.getSize();
			maxEntrySize = zipEntrySize <= maxEntrySize ? maxEntrySize : zipEntrySize;
		}

		zipInputStream.close();
		maxEntrySize++;
		classByteBuffer = new byte[maxEntrySize];
	}

	public synchronized Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException {
		Class resultClass = (Class) cachedClasses.get(className);
		if (resultClass != null)
			return resultClass;
		boolean isApplicationClass = false;
		String applicationPackage;
		for (Enumeration enumer = applicationPackages.elements(); enumer.hasMoreElements() && !isApplicationClass; isApplicationClass = className
				.startsWith(applicationPackage))
			applicationPackage = (String) enumer.nextElement();

		if (!isApplicationClass)
			try {
				resultClass = super.findSystemClass(className);
				return resultClass;
			} catch (ClassNotFoundException classnotfoundexception) {
			}
		byte classBytes[] = loadFileBytes(className);
		if (classBytes == null)
			throw new ClassNotFoundException();
		resultClass = defineClass(className, classBytes, 0, classBytes.length);
		if (resultClass == null)
			throw new ClassFormatError();
		if (resolveIt)
			resolveClass(resultClass);
		cachedClasses.put(className, resultClass);
		return resultClass;
	}

	private byte[] loadFileBytes(String className) {
		try {
			String searchZipEntryName = className.replace('.', '/') + ".class";
			ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilename));
			ZipEntry zipEntry;
			for (zipEntry = zipInputStream.getNextEntry(); zipEntry != null
					&& !zipEntry.getName().equals(searchZipEntryName); zipEntry = zipInputStream.getNextEntry())
				;
			if (zipEntry == null)
				return null;
			int totalBytesRead = 0;
			for (int bytesReadThisTime = 0; bytesReadThisTime != -1;) {
				bytesReadThisTime = zipInputStream.read(classByteBuffer, totalBytesRead, classByteBuffer.length
						- totalBytesRead);
				if (bytesReadThisTime != -1)
					totalBytesRead += bytesReadThisTime;
			}

			zipInputStream.close();
			byte classBytes[] = new byte[totalBytesRead];
			for (int i = 0; i < totalBytesRead; i++)
				classBytes[i] = classByteBuffer[i];

			return classBytes;
		} catch (IOException e) {
			return null;
		}
	}
}
